BINDING_HEADER_KONG = "Kong Automatic UI Hider";
BINDING_NAME_KONG_ENABLE = "Enable UI Fading";
BINDING_NAME_KONG_DISABLE = "Disable UI Fading";
BINDING_NAME_KONG_TOGGLE = "Toggle UI Fading On/Off";
local VERSION = "1.4";

Kong_Frames = {};
Kong_Profiles = {};

local FadeFrames = {};
local LastFocus = nil;
local LastRoot = nil;

local CANNOT_SAVE = "Nameless frames cannot be saved.";
local UNKNOWN_OPTION = "Unknown option: %q (Type |cFF33FF99/kong %s|r for help).";
local PROFILE_NAME_REQUIRED = "Profile name required.";
local NO_SUCH_PROFILE = "Profile %q does not exist. Use |cFF33FF99/kong profile list|r to see a list of available profiles.";
local PROFILE_LOADED = "Profile %q loaded.";
local NOW_USING_PROFILE = "Now using profile %q.";
local PROFILE_ALREADY_EXISTS = "Profile %q already exists.";
local CANNOT_DELETE_CURRENT_PROFILE = "Cannot delete the current profile. Use |cFF33FF99/kong profile reset|r to reset it.";
local PROFILE_ALREADY_LOADED = "Profile %q is already the current profile.";
local PROFILE_DELETED = "Profile %q deleted.";
local PROFILE_RESET = "Current profile %q has been reset.";
local HELP_PROFILE_COMMANDS = "|cFFBBBBFFKong Automatic UI Hider|r profile commands ( |cFF33FF99/kong profile|r ):";
local HELP_PROFILE_LOAD = "|cFF33FF99load <name>|r: loads the specified profile.";
local HELP_PROFILE_CREATE = "|cFF33FF99create <name>|r: creates a new profile with the given name.";
local HELP_PROFILE_DELETE = "|cFF33FF99delete <name>|r: deletes the specified profile.";
local HELP_PROFILE_LIST = "|cFF33FF99list|r: lists all available profiles.";
local HELP_PROFILE_CURRENT = "|cFF33FF99current|r: prints the current profile name.";
local HELP_PROFILE_RESET = "|cFF33FF99reset|r: resets the current profile.";
local INVALID_COMMAND = " command unknown (Type |cFF33FF99/kong help|r for help)."
local NO_KONG_CONFIG = "Kong Config is either disabled or not installed. Defaulting to command-line interface..."
local HELP_COMMANDS = "|cFFBBBBFFKong Automatic UI Hider|r commands ( |cFF33FF99/kong|r ):";
local HELP_TOGGLE = "|cFF33FF99enable / disable / toggle|r: enables or disables fading of the user interface.";
local HELP_PROFILE = "|cFF33FF99profile|r: manages saved Kong configurations and allows for configuration sharing between characters.";
local ENABLED = "Enabled.";
local ALREADY_ENABLED = "Already enabled."
local DISABLED = "Disabled.";
local ALREADY_DISABLED = "Already disabled.";

StaticPopupDialogs["KONG_RESET"] = {
    text = "Are you sure you want to delete all settings from the Kong profile \"%s\"?",
    button1 = "Reset",
    button2 = CANCEL,
    OnAccept = function(self)
    
        Kong_Unload();
        Kong_Profiles[Kong_Settings.profile] = nil
        Kong_LoadSettings();
        
        Kong_Info(PROFILE_RESET, Kong_Settings.profile);
    end,
    showAlert = 1,
    timeout = 0,
    whileDead = true,
    hideOnEscape = true,
}

-- The defaults for all triggers
local TriggerDefaults = {
    secondsIn = .2,
    secondsOut = 1,
    alphaIn = 1,
    alphaOut = 0,
};

local pairs = _G.pairs;
local ipairs = _G.ipairs;

function Kong_Error(str, ...)
    DEFAULT_CHAT_FRAME:AddMessage("|cFFFF2222Kong Error:|r "..string.format(str, ...));
end

function Kong_Warning(str, ...)
    DEFAULT_CHAT_FRAME:AddMessage("|cFFFF9922Kong Warning:|r "..string.format(str, ...));
end

function Kong_Info(str, ...)
    DEFAULT_CHAT_FRAME:AddMessage("|cFF33FF99Kong:|r "..string.format(str, ...));
end

local LibUSC = LibStub("LibUserSpecifiedConditions", true);
if not LibUSC then
    Kong_Error("Kong does not appear to have been installed correctly. Note: A full restart of "
             .."World of Warcraft is required when upgrading from a previous version of Kong.")
    return
end
    
-- Dummy condition
MouseoverTypeName = "Frame has Mouse Focus"
LibUSC:RegisterParameter(MouseoverTypeName, { 
    type = "Truth",
    isHidden = true,
    value = false,
})

function Kong_OverrideAlpha(Root, override)

    if Kong_Frames[Root] then
        if override then 
            Root.kongDoOverrideAlphaHook = true
            Root.kongFaderAlpha = Root:GetAlpha()
            Root.kongFrameAlpha = Root:GetAlpha()
            Root.KongSetAlpha = function(frame, alpha)
                frame.kongDoOverrideAlphaHook = false
                frame.kongFaderAlpha = alpha
                frame:SetAlpha(math.min(frame.kongFrameAlpha, alpha));
                frame.kongDoOverrideAlphaHook = true
            end
            
            -- Only need to actually hook once per session
            if not Root.kongAlphaOverridden then
                hooksecurefunc(Root, "SetAlpha", function(self, alpha) 
                    if self.kongDoOverrideAlphaHook then
                        self.kongDoOverrideAlphaHook = false
                        self.kongFrameAlpha = alpha
                        self:SetAlpha(math.min(alpha, self.kongFaderAlpha))
                        self.kongDoOverrideAlphaHook = true
                    end
                end)
                Root.kongAlphaOverridden = true
            end
        else
            Root.kongDoOverrideAlphaHook = nil
            Root.kongFaderAlpha = nil
            Root.kongFrameAlpha = nil
            Root.KongSetAlpha = Root.SetAlpha
        end
    end
end

function Kong_CallOnFrameHeirarchy(Frames, funcName, ...)
    for _, Frame in pairs(Frames) do
        Frame[funcName](Frame, ...)
        Kong_CallOnFrameHeirarchy({Frame:GetChildren()}, funcName, ...)
    end
end

function Kong_DisableDraw(Frame)
    local Frames = {Frame}
    Kong_CallOnFrameHeirarchy(Frames, "DisableDrawLayer", "BACKGROUND")
    Kong_CallOnFrameHeirarchy(Frames, "DisableDrawLayer", "BORDER")
    Kong_CallOnFrameHeirarchy(Frames, "DisableDrawLayer", "ARTWORK")
    Kong_CallOnFrameHeirarchy(Frames, "DisableDrawLayer", "OVERLAY")
end

function Kong_EnableDraw(Frame) 
    local Frames = {Frame}
    Kong_CallOnFrameHeirarchy(Frames, "EnableDrawLayer", "BACKGROUND")
    Kong_CallOnFrameHeirarchy(Frames, "EnableDrawLayer", "BORDER")
    Kong_CallOnFrameHeirarchy(Frames, "EnableDrawLayer", "ARTWORK")
    Kong_CallOnFrameHeirarchy(Frames, "EnableDrawLayer", "OVERLAY")
    
end
    
function Kong_DoFade(Frame, mode, startAlpha, endAlpha, duration, finishedFunc)

    Frame.KongAnimationGroup:Stop()
    if Kong_Settings.enabled and not (KongConfig and KongConfig:IsShown()) then
        if duration > 0 then
            Frame:KongSetAlpha(startAlpha)
        
            Frame.KongAlphaAnimation:SetChange(endAlpha-startAlpha)
            Frame.KongAlphaAnimation:SetDuration(duration)
            
            Frame.KongAnimationGroup:Play()
            Frame.KongAnimationGroup:SetScript("OnFinished", function(self)
                Frame:KongSetAlpha(endAlpha)
            end)
        else
            Frame:KongSetAlpha(endAlpha)
        end
    end

-- print("Fading", Frame:GetName(), mode, "from", startAlpha, "to", endAlpha, "over", duration, "seconds");
end

function Kong_FadeIn(Frame, Trigger)

    Trigger.refs = Trigger.refs + 1;
    
    if Trigger.refs == 1 then
        Kong_CalculateFadeIn(Frame);
    end
end

function Kong_CalculateFadeIn(Frame)
    local currentAlpha = ceil(Frame:KongGetAlpha()*100)/100;
    local fastestRate = 0;
    local endAlpha = 1;
    local duration;
    local finishedFunc = nil;

    -- Iterate thru all possible triggers
    for _,Trigger in ipairs(Kong_Frames[Frame]) do
        -- Only triggers that are currently fading in
        if Trigger.refs > 0 and Trigger.alphaIn >= currentAlpha then
        
            -- Only fade in if alpha is increasing
            if Trigger.alphaIn > currentAlpha then
                finishedFunc = Kong_CalculateFadeIn
            end
    
            -- Find the fastest fader
            local rate = (Trigger.alphaIn - Trigger.alphaOut) / Trigger.secondsIn;
            if rate > fastestRate or (rate == fastestRate and Trigger.alphaIn > endAlpha) then
                fastestRate = rate;
                endAlpha = Trigger.alphaIn;
                duration = (endAlpha - currentAlpha) / rate;
            end
        end
    end
    
    if (fastestRate > 0) then
        Kong_DoFade(Frame, "IN", currentAlpha, endAlpha, duration, finishedFunc);
        
        if Kong_Frames[Frame].disableDrawWhenHidden and (currentAlpha == 0) then
            Kong_EnableDraw(Frame)
        end
    end
end

function Kong_FadeOut(Frame, Trigger)

    Trigger.refs = Trigger.refs - 1;
    
    assert(Trigger.refs >= 0, "ref < 0 for frame: "..tostring(Frame:GetName()));

    if Trigger.refs == 0 then
    
        -- Adjust fade time for the case where the trigger was still fading in
        local rate = (Trigger.alphaOut-Trigger.alphaIn)/Trigger.secondsOut;
        Trigger.lastFadeTime = GetTime() + (Trigger.alphaIn - Frame:KongGetAlpha()) / rate;

        Kong_CalculateFadeOut(Frame);
    end
end

function Kong_CalculateFadeOut(Frame)

    local currentTime = GetTime();
    local currentAlpha = Frame:KongGetAlpha();
    local highestAlpha = Kong_Frames[Frame].TriggerDefaults.alphaOut;
    local slowestRate = math.huge;
    local highestTrigger;
    local startAlpha = Kong_Frames[Frame].TriggerDefaults.alphaOut;
    local endAlpha = startAlpha;
    local duration = 0;
    local finishedFunc = nil;
    
    -- Find the fader with the highest current alpha
    for _, Trigger in ipairs(Kong_Frames[Frame]) do
        if Trigger.refs > 0 then
            if Trigger.alphaIn >= currentAlpha then
                return; -- An active trigger has higher alpha, dont need to fade out
            else
                -- Dont fade past the highest active trigger alpha
                endAlpha = max(endAlpha, Trigger.alphaIn);
            end
        elseif Trigger.lastFadeTime then
            local fadeAge = currentTime-Trigger.lastFadeTime;
            -- If the trigger is fading out
            if fadeAge < Trigger.secondsOut then
                local rate = (Trigger.alphaOut-Trigger.alphaIn)/Trigger.secondsOut;
                local currentFaderAlpha = Trigger.alphaIn + rate*fadeAge;
                startAlpha = max(startAlpha, currentFaderAlpha);
                -- Dont fade if there is an active trigger with higher alpha than this fader's current alpha
                if currentFaderAlpha > endAlpha then
                    -- Use the trigger with the highest current alpha
                    if (currentFaderAlpha > highestAlpha)
                    or (currentFaderAlpha == highestAlpha and rate < slowestRate) then
                        highestAlpha = currentFaderAlpha;
                        slowestRate = rate;
                        highestTrigger = Trigger;
                    end
                end
            end
        end
    end
    startAlpha = min(startAlpha, currentAlpha);

    -- If a fader was found
    if (highestTrigger) then
        -- Find the highest point of intersection with other faders
        local rate1 = (highestTrigger.alphaOut-highestTrigger.alphaIn)/highestTrigger.secondsOut;
        local alpha1 = highestTrigger.alphaIn + rate1*(currentTime-highestTrigger.lastFadeTime);
        for _,Trigger in ipairs(Kong_Frames[Frame]) do
            if Trigger ~= highestTrigger then
                if Trigger.lastFadeTime then
                    local fadeAge = currentTime-Trigger.lastFadeTime;
                    if fadeAge < Trigger.secondsOut then
                        local rate2 = (Trigger.alphaOut-Trigger.alphaIn)/Trigger.secondsOut;
                        local alpha2 = Trigger.alphaIn + rate2*fadeAge;

                        local interceptTime = (alpha2-alpha1)/(rate1-rate2);
                        if interceptTime > 0 and interceptTime < highestTrigger.secondsOut
                                                 -(currentTime-highestTrigger.lastFadeTime) then
                            -- Dont fade past the intersection with a slower fader
                            local interceptAlpha = highestTrigger.alphaIn
                            + rate1*(currentTime-highestTrigger.lastFadeTime)
                            + rate1*interceptTime - .01; -- .01 extra alpha to make sure intercept is actually met
                            endAlpha = max(endAlpha, interceptAlpha);
                            finishedFunc = Kong_CalculateFadeOut;
                        end
                    end
                end
            end
        end
        duration = (endAlpha-startAlpha) / rate1;
    end

    if Kong_Frames[Frame].disableDrawWhenHidden and (endAlpha == 0) then 
        finishedFunc = Kong_DisableDraw
    end
    
    Kong_DoFade(Frame, "OUT", startAlpha, endAlpha, duration, finishedFunc)
end

function Kong_GetRoot(Frame)

    local Parent = Frame;
    while Parent and Parent ~= UIParent do
        Frame = Parent;
        Parent = Frame:GetParent();
    end

    return Frame;
end

function Kong_FindGlobalIndex(Frame)

    local name = Frame:GetName();
    if name then
        return "[\"" .. name .. "\"]";
    end

    for i,x in pairs(_G) do
        if x == Frame then
            if type(i) == "string" then
                return "[\"" .. i .. "\"]";
            elseif type(i) == "number" or type(i) == "boolean" then
                return "[".. tostring(i) .. "]"
            end
        end
    end
    
    for _, Child in pairs({Frame:GetChildren()}) do
        name = Kong_FindGlobalIndex(Child);
        if name then
            return name;
        end
    end
end

function Kong_CreateTrigger(Frame)

    local Trigger = {};
    setmetatable(Trigger, {__index = Kong_Frames[Frame].TriggerDefaults or TriggerDefaults});
    
    if Kong_Frames[Frame].TriggerDefaults then
        Trigger.refs = 0;
    else
        Trigger.Frame = Frame
    end
    
    return Trigger;
end

function Kong_RegisterFrame(Root)

    -- Associate kong with the frame
    Kong_Frames[Root] = {}

    Root.KongGetAlpha = Root.GetAlpha
    Root.KongSetAlpha = Root.SetAlpha
    Root.KongAnimationGroup = Root.KongAnimationGroup or Root:CreateAnimationGroup()
    Root.KongAlphaAnimation = Root.KongAlphaAnimation 
                           or Root.KongAnimationGroup:CreateAnimation("Alpha")
    
    -- Setup triggers
    Kong_Frames[Root].TriggerDefaults = Kong_CreateTrigger(Root)
    Kong_Frames[Root].Mouseover = 
        Kong_AddTrigger(Root, LibUSC:Create("Condition", LibUSC:Create(MouseoverTypeName)))
    Kong_AddTrigger(Root, LibUSC:Create("Condition", LibUSC:Create("Unit in Combat")))
    Kong_AddTrigger(Root, LibUSC:Create("Condition", LibUSC:Create("Unit in Vehicle")))

    -- Fade according to current state
    if Kong_Settings.enabled then
        Root:KongSetAlpha(Kong_Frames[Root].TriggerDefaults.alphaOut);
    end

    -- Attempt to save settings between sessions
    local index = Kong_FindGlobalIndex(Root);
    if index then
        Kong_Frames[Root].index = index;
    else
        Kong_Frames[Root].index = {}; -- unique grouping index for this session only
        Kong_Warning(CANNOT_SAVE);
    end
end

function Kong_UnregisterFrame(Root)

    Root.KongAnimationGroup:Stop()
    Kong_OverrideAlpha(Root, false)
    Root:KongSetAlpha(1);
    Root.KongGetAlpha = nil
    Root.KongSetAlpha = nil
    
    local index = Kong_Frames[Root].index;
    
    -- For any groups that this frame was part of
    for groupName,Group in pairs(Kong_Settings.Groups) do 
        if Group[index] then

            Group[index] = nil;
        
            -- Delete the group if empty
            if not next(Group) then
                Kong_Settings.Groups[groupName] = nil;
            end
        end
    end
    Kong_Frames[Root] = nil;
end

function Kong_OnConditionStateChange(Root, Trigger)

    if Trigger.Condition:IsMet() then
        Kong_FadeIn(Root, Trigger)
    else
        Kong_FadeOut(Root, Trigger)
    end
end

function Kong_AddTrigger(Root, Condition)

    local Trigger = Kong_CreateTrigger(Root)

    Trigger.Condition = Condition
    Trigger.Condition:AddListener(Kong_OnConditionStateChange, Root, Trigger)

    tinsert(Kong_Frames[Root], Trigger)
    
    if Trigger.Condition:IsMet() then
        Kong_FadeIn(Root, Trigger)
    end
    
    return Trigger
end

function Kong_RemoveTrigger(Root, Trigger)

    if Trigger.Condition:IsMet() then
        Kong_FadeOut(Root, Trigger)
    end
    
    LibUSC:Deflate(Trigger.Condition)
    
    for i, v in ipairs(Kong_Frames[Root]) do
        if v == Trigger then
            tremove(Kong_Frames[Root], i)
            break
        end
    end
end

function Kong_AddFrameToGroup(Root, name)

    if not Kong_Settings.Groups[name] then
        -- Create the group if it doesn't exist yet
        Kong_Settings.Groups[name] = {};
    end
    
    -- Make sure the frame isn't already in the group
    if not Kong_Settings.Groups[name][Kong_Frames[Root].index] then
    
        Kong_Settings.Groups[name][Kong_Frames[Root].index] = Root;
        return true;
    end
end

function Kong_RemoveFrameFromGroup(Root, name)

    local Group = Kong_Settings.Groups[name];
    if Group then
        if Group[Kong_Frames[Root].index] then
        
            Group[Kong_Frames[Root].index] = nil;
            if not next(Group) then
                Kong_Settings.Groups[name] = nil;
            end
            return true
        end
    end
end
  
function Kong_Enable()
    Kong_Settings.enabled = true;
    for Frame in pairs(Kong_Frames) do
        Kong_CalculateFadeOut(Frame);
    end
    KongUIFader:SetScript("OnUpdate", Kong_OnUpdate);
end

function Kong_Disable()
    Kong_Settings.enabled = false;
    for Frame in pairs(Kong_Frames) do
        if Kong_Frames[Frame].disableDrawWhenHidden and (Frame:KongGetAlpha() == 0) then
            Kong_EnableDraw(Frame)
        end
        Frame.KongAnimationGroup:Stop()
        Frame:KongSetAlpha(1);
    end
    KongUIFader:SetScript("OnUpdate", nil);
end

function Kong_RenameFrame(Root, path)

    -- Update groups containing the frame
    for _,Group in pairs(Kong_Settings.Groups) do
        if Group[Kong_Frames[Root].index] then
            Group[Kong_Frames[Root].index] = nil
            Group[path] = Root
        end
    end
    
    -- Update references to frame name
    Kong_Frames[Root].index = path
end

function Kong_LoadSettings()

    -- Only need to initialize once at start and for new profiles
    if Kong_Initialized then
        return
    end
    Kong_Initialized = true
    
    -- Replace spaces in profiles created prior to v0.9c
    local TempProfiles = {};
    for n, p in pairs(Kong_Profiles) do 
        if string.find(n, " ") then
            TempProfiles[string.gsub(n," ","_")] = p;
            Kong_Profiles[n] = nil;
        end
    end
    for n, p in pairs(TempProfiles) do
        Kong_Profiles[n] = TempProfiles[n];
    end
    
    local defaultProfile = string.lower(UnitName("player"))
        .."_of_"..string.lower(string.gsub(GetRealmName()," ","_"));
    local enabled = true;
    
    -- Convert from previous version
    if Kong_Settings and Kong_Settings.version then
    
        local version = gsub(Kong_Settings.version,"%a","")
        version = tonumber(version);
        
        if version >= 0.7 and version < 0.9 then
            if not Kong_Settings.Groups then
                Kong_Settings.Groups = {};
            end
            
            -- TriggerDefaults.refs was being saved in version 0.7
            for _,Frame in pairs(Kong_Settings.Frames) do
                Frame.TriggerDefaults.refs = nil;
            end
            
            -- Convert current settings to a profile
            enabled = Kong_Settings.enabled;
            Kong_Settings.enabled = nil;
            Kong_Settings.version = VERSION;
            Kong_Profiles[defaultProfile] = Kong_Settings;
        end
    end
    
    if (not Kong_Settings) or (not Kong_Settings.profile) then
        Kong_Settings = {
            profile = defaultProfile,
            enabled = enabled,
        };
    end
    Kong_Settings.profile = string.gsub(Kong_Settings.profile," ","_");
        
    -- If creating a new profiles
    if not Kong_Profiles[Kong_Settings.profile] then
        Kong_Profiles[Kong_Settings.profile] = {
            Frames = {},
            Groups = {},
        };
    end
    Kong_Profiles[Kong_Settings.profile].version = VERSION
    
    -- Kong_Settings will have all the same fields as the profile
    setmetatable(Kong_Settings, {__index = Kong_Profiles[Kong_Settings.profile]});
    
    -- Index frames by number (for pre-v1.1 compatibility)
    local tempFrames = Kong_Settings.Frames;
    Kong_Profiles[Kong_Settings.profile].Frames = {}
    for _, SavedFrame in pairs(tempFrames) do
        table.insert(Kong_Settings.Frames, SavedFrame)
    end
    
    -- Initialize group-frames to 0 (for pre-v1.1 compatibility)
    for _,Group in pairs(Kong_Settings.Groups) do
        for index in pairs(Group) do
            Group[index] = 0;
        end
    end
    
    -- Convert pre-v1.2 indexes
    for _, SavedFrame in pairs(Kong_Settings.Frames) do
        if string.sub(SavedFrame.index, 1, 1) ~= "[" then
            SavedFrame.index = gsub(SavedFrame.index, "%.", "[\"")
            SavedFrame.index = gsub(SavedFrame.index, "%[", "\"][")
            SavedFrame.index = gsub("[\""..SavedFrame.index.."\"]", "%]\"%]", "]")
        end
    end
    for groupName, Group in pairs(Kong_Settings.Groups) do
        local tempGroup = Group
        Kong_Settings.Groups[groupName] = {}
        for index in pairs(tempGroup) do
            if string.sub(index, 1, 1) ~= "[" then
                index = gsub(index, "%.", "[\"")
                index = gsub(index, "%[", "\"][")
                index = gsub("[\""..index.."\"]", "%]\"%]", "]")
            end
            Kong_Settings.Groups[groupName][index] = 0;
        end
    end
    
    -- Convert pre-v1.3 triggers
    for _, SavedFrame in pairs(Kong_Settings.Frames) do 
        for _, triggerName in ipairs({"mouseover", "combat", "nocombat", "vehicle", "novehicle", 
                                      "casting", "nocasting", "macro"}) do
        
            local Trigger = SavedFrame[triggerName]
            if Trigger then
                if "mouseover" == triggerName then
                    Trigger.Condition = LibUSC:Create("Condition", 
                        LibUSC:Create("Frame has Mouse Focus"))
                elseif "combat" == triggerName then
                    Trigger.Condition = LibUSC:Create("Condition", 
                        LibUSC:Create("Unit in Combat"))
                elseif "nocombat" == triggerName then
                    Trigger.Condition = LibUSC:Create("Condition", 
                        LibUSC:Create("Not",
                            LibUSC:Create("Unit in Combat")))
                elseif "vehicle" == triggerName then
                    Trigger.Condition = LibUSC:Create("Condition", 
                        LibUSC:Create("Unit in Vehicle"))
                elseif "novehicle" == triggerName then
                    Trigger.Condition = LibUSC:Create("Condition", 
                        LibUSC:Create("Not",
                            LibUSC:Create("Unit in Vehicle")))
                elseif "casting" == triggerName then
                    Trigger.Condition = LibUSC:Create("Condition", 
                        LibUSC:Create("Unit is Casting"))
                elseif "nocasting" == triggerName then
                    Trigger.Condition = LibUSC:Create("Condition", 
                        LibUSC:Create("Not",
                            LibUSC:Create("Unit is Casting")))
                elseif "macro" == triggerName then
                    Trigger.Condition = LibUSC:Create("Condition", 
                        LibUSC:Create("Macro",
                            LibUSC:Create("User-Entered Text", Trigger.condition)))
                    Trigger.condition = nil
                end
                tinsert(SavedFrame, Trigger)
                SavedFrame[triggerName] = nil
            end
            
            Trigger = SavedFrame.health
            if Trigger then
                for unit in pairs(Trigger.Units) do
                    local TriggerCopy = CopyTable(Trigger)
                    if Trigger.lessThan then
                        TriggerCopy.Condition = LibUSC:Create("Condition", 
                            LibUSC:Create("Less Than",
                                LibUSC:Create("Unit Health (Percent)",
                                    LibUSC:Create("Convert Text to Unit",
                                        LibUSC:Create("User-Entered Text", unit))),
                                LibUSC:Create("User-Entered Number", Trigger.threshold)))
                    else
                        TriggerCopy.Condition = LibUSC:Create("Condition", 
                            LibUSC:Create("Greater Than",
                                LibUSC:Create("Unit Health (Percent)",
                                    LibUSC:Create("Convert Text to Unit",
                                        LibUSC:Create("User-Entered Text", unit))),
                                LibUSC:Create("User-Entered Number", Trigger.threshold)))
                    end
                    TriggerCopy.Units = nil
                    TriggerCopy.lessThan = nil
                    TriggerCopy.threshold = nil
                    tinsert(SavedFrame, TriggerCopy)
                end
                SavedFrame.health = nil
            end
        end
    end
    
    Kong_LoadFrames()
    
    if not Kong_Settings.enabled then
       Kong_Warning("Kong is currently disabled. Use |cFF33FF99/kong toggle|r to enable it.")
    end
end

function Kong_LoadFrames()

    for i=#Kong_Settings.Frames,1,-1 do

        local SavedFrame = Kong_Settings.Frames[i]
        
        -- Convert stored path to frame
        if not SavedFrame.loader then
            SavedFrame.loader = loadstring("return _G"..SavedFrame.index)
        end
        
        local Frame = nil;
        pcall(function() Frame = SavedFrame.loader(); end)
        
        if Frame then

            local Root = Kong_GetRoot(Frame);
            if not Kong_Frames[Root] then
                
                table.remove(Kong_Settings.Frames, i)
                SavedFrame.loader = nil
                Kong_Frames[Root] = SavedFrame;
                
                setmetatable(SavedFrame.TriggerDefaults, {__index = TriggerDefaults});
                SavedFrame.TriggerDefaults.Frame = Root
                
                Root.KongGetAlpha = Root.GetAlpha
                Root.KongSetAlpha = Root.SetAlpha
                if SavedFrame.overrideAlpha then
                    Kong_OverrideAlpha(Root, true)
                end
                Root.KongAnimationGroup = Root.KongAnimationGroup or Root:CreateAnimationGroup()
                Root.KongAlphaAnimation = Root.KongAlphaAnimation 
                                       or Root.KongAnimationGroup:CreateAnimation("Alpha")
                
                -- Initialize each trigger
                for _,Trigger in ipairs(SavedFrame) do
                    setmetatable(Trigger, {__index = SavedFrame.TriggerDefaults});
                    Trigger.refs = 0;
                    LibUSC:Inflate(Trigger.Condition)
                    Trigger.Condition:AddListener(Kong_OnConditionStateChange, Root, Trigger)
                    if MouseoverTypeName == Trigger.Condition.Parameters[1].name then
                        SavedFrame.Mouseover = Trigger
                    end
                end
                
                if Kong_Settings.enabled then
                    Kong_CalculateFadeOut(Root);
                end
                
                -- Fade frame according to current state
                for _,Trigger in ipairs(SavedFrame) do
                    if Trigger.Condition:IsMet() then
                        Kong_FadeIn(Root, Trigger);
                    end
                end

                -- Connect the mouseover group list to actual frames
                for groupName,Group in pairs(Kong_Settings.Groups) do
                    if Group[SavedFrame.index] then
                    
                        -- Fade in the frame if the group is active
                        for i=1,Group[SavedFrame.index] do
                            Kong_FadeIn(Root, SavedFrame.Mouseover);
                        end
                        
                        Group[SavedFrame.index] = Root;
                    end
                end
            end
        end
    end
end

function Kong_Unload()

    for Root, Settings in pairs(Kong_Frames) do

        if Kong_Frames[Root].disableDrawWhenHidden and (Root:KongGetAlpha() == 0) then
            Kong_EnableDraw(Root)
        end
            
        Root.KongAnimationGroup:Stop()
        Kong_OverrideAlpha(Root, false)
        Root:KongSetAlpha(1);
        Root.KongGetAlpha = nil
        Root.KongSetAlpha = nil
        
        -- Remove runtime-specific data
        for _,trigger in ipairs(Settings) do
            trigger.refs = nil
            trigger.lastFadeTime = nil
            LibUSC:Deflate(trigger.Condition)
        end
        Settings.loader = nil
        Settings.Mouseover = nil
        Settings.TriggerDefaults.Frame = nil
        
        -- Move it back to Kong_Settings to be saved
        Kong_Frames[Root] = nil;
        if type(Settings.index) ~= "table" then
            table.insert(Kong_Settings.Frames, Settings);
        end
    end
    
    MinimapCluster.KongAlphaAnimation:GetScript("OnFinished")()
    
    -- "disconnect" the groups from the actual frames
    for _,Group in pairs(Kong_Settings.Groups) do
        for index in pairs(Group) do
            Group[index] = 0
        end
    end
    
    -- In case a new profile is about to be loaded
    Kong_Frames = {};
    FadeFrames = {};
    LastFocus = nil;
    LastRoot = nil;
    Kong_Initialized = false;
end

function Kong_ProfileCommandHandler(args)

    operation = args[2];
    if #args > 2 then
        profile = table.concat(args, "_", 3);
    end
    
    if not operation then
    
        DEFAULT_CHAT_FRAME:AddMessage(string.format(HELP_PROFILE_COMMANDS));
        DEFAULT_CHAT_FRAME:AddMessage(string.format(HELP_PROFILE_LOAD));
        DEFAULT_CHAT_FRAME:AddMessage(string.format(HELP_PROFILE_CREATE));
        DEFAULT_CHAT_FRAME:AddMessage(string.format(HELP_PROFILE_DELETE));
        DEFAULT_CHAT_FRAME:AddMessage(string.format(HELP_PROFILE_LIST));
        DEFAULT_CHAT_FRAME:AddMessage(string.format(HELP_PROFILE_CURRENT));
        DEFAULT_CHAT_FRAME:AddMessage(string.format(HELP_PROFILE_RESET));
    
    elseif operation == "load" then
    
        if profile then
            if Kong_Profiles[profile] then
                if profile ~= Kong_Settings.profile then
                
                    Kong_Unload();
                    Kong_Settings.profile = profile;
                    Kong_LoadSettings();
                    
                    Kong_Info(PROFILE_LOADED, profile);
                else
                    Kong_Error(PROFILE_ALREADY_LOADED, profile);
                end
            else
                Kong_Error(NO_SUCH_PROFILE, profile);
            end
        else
            Kong_Error(PROFILE_NAME_REQUIRED);
        end
    elseif operation == "create" then
    
        if profile then
            if not Kong_Profiles[profile] then
            
                Kong_Unload();
                Kong_Settings.profile = profile;
                Kong_LoadSettings();
                
                Kong_Info(NOW_USING_PROFILE, profile);
            else 
                Kong_Error(PROFILE_ALREADY_EXISTS, profile);
            end
        else
            Kong_Error(PROFILE_NAME_REQUIRED);
        end
    elseif operation == "list" then
        local ProfileNames = {};
        for name in pairs(Kong_Profiles) do
            tinsert(ProfileNames, name);
        end
        Kong_Info("Available profiles: "..table.concat(ProfileNames, ", "));
        
    elseif operation == "current" then
    
        Kong_Info("Current profile is: "..Kong_Settings.profile);
        
    elseif operation == "delete" then
    
        if profile then
            if profile ~= Kong_Settings.profile then
                if Kong_Profiles[profile] then
                    Kong_Profiles[profile] = nil;
                    Kong_Info(PROFILE_DELETED, profile);
                else
                    Kong_Error(NO_SUCH_PROFILE, profile);
                end
            else
                Kong_Error(CANNOT_DELETE_CURRENT_PROFILE);
            end
        else
            Kong_Error(PROFILE_NAME_REQUIRED);
        end
    elseif operation == "reset" then
        Kong_ResetCommandHandler()
    else
        Kong_Error(UNKNOWN_OPTION, operation, args[1]);
    end
end

function Kong_ResetCommandHandler()
    StaticPopup_Show("KONG_RESET", Kong_Settings.profile)
end

function Kong_EnableCommandHandler()
    if not Kong_Settings.enabled then
        Kong_Enable()
        Kong_Info(ENABLED);
    else
        Kong_Error(ALREADY_ENABLED);
    end
end

function Kong_DisableCommandHandler()
    if Kong_Settings.enabled then
        Kong_Disable()
        Kong_Info(DISABLED);
    else
        Kong_Error(ALREADY_DISABLED);
    end
end

function Kong_ToggleCommandHandler()
    if Kong_Settings.enabled then
        Kong_DisableCommandHandler();
    else
        Kong_EnableCommandHandler();
    end
end

function Kong_CommandHandler(args)
    local args = {strsplit(" ", string.lower(args))};
    local command = args[1];

    if command and command ~= "" and KongConfig and KongConfig.frame:IsShown() then 
        Kong_Error("The config screen must be closed to issue /kong commands.")
    elseif command == "enable" then
        Kong_EnableCommandHandler();
    elseif command == "disable" then
        Kong_DisableCommandHandler();
    elseif command == "toggle" then
        Kong_ToggleCommandHandler();
    elseif command == "profile" then
        Kong_ProfileCommandHandler(args);
    elseif command == "reset" then
        Kong_ResetCommandHandler(args)
    elseif command and command ~= "" and command ~= "help" then
        Kong_Error("\""..command.."\""..INVALID_COMMAND);
    elseif command ~= "help" and LoadAddOn('KongConfig') then
            KongConfig_Show()
    else
        if command ~= "help" then
            Kong_Warning(NO_KONG_CONFIG);
        end
        DEFAULT_CHAT_FRAME:AddMessage(HELP_COMMANDS);
        DEFAULT_CHAT_FRAME:AddMessage(HELP_TOGGLE);
        DEFAULT_CHAT_FRAME:AddMessage(HELP_PROFILE);
    end
end
SLASH_KONG1 = "/kong";
SlashCmdList["KONG"] = Kong_CommandHandler;

local FRAME_FIND_PERIOD = 1.0
local frameFindTimer = 0
function Kong_OnUpdate(self, elapsed)

    frameFindTimer = frameFindTimer - elapsed
    if frameFindTimer <= 0 then
        frameFindTimer = FRAME_FIND_PERIOD
        
        -- Load any missing frames
        Kong_LoadFrames()
    end

    -- Did the mouse focus change?
    local Focus = GetMouseFocus();
    if Focus ~= LastFocus then
    
        -- Store the focus
        LastFocus = Focus;

        -- Did the mouseover frame root change?
        local FocusRoot = Kong_GetRoot(Focus);
        if LastRoot ~= FocusRoot then
        
            -- If the new mouseover root is registered with Kong
            if FocusRoot and Kong_Frames[FocusRoot] then
                -- Fade in the mouseover frame group(s) that it's a member of
                for _,Group in pairs(Kong_Settings.Groups) do
                    if Group[Kong_Frames[FocusRoot].index] then
                        for index,GroupFrame in pairs(Group) do
                            if type(GroupFrame) == "table" then
                                Kong_FadeIn(GroupFrame, Kong_Frames[GroupFrame].Mouseover);
                            else
                                Group[index] = GroupFrame + 1
                            end
                        end
                    end
                end
                Kong_FadeIn(FocusRoot, Kong_Frames[FocusRoot].Mouseover);
            end

            -- If the old mouseover root is registered with Kong
            if LastRoot and Kong_Frames[LastRoot] then
                -- Fade out the mouseover frame group(s) that it's a member of
                for _,Group in pairs(Kong_Settings.Groups) do
                    if Group[Kong_Frames[LastRoot].index] then
                        for index,GroupFrame in pairs(Group) do
                            if type(GroupFrame) == "table" then
                                Kong_FadeOut(GroupFrame, Kong_Frames[GroupFrame].Mouseover);
                            else
                                Group[index] = GroupFrame - 1
                            end
                        end
                    end
                end
                Kong_FadeOut(LastRoot, Kong_Frames[LastRoot].Mouseover);
            end

            LastRoot = FocusRoot;
        end
    end
end

function Kong_OnEvent(self, event, arg1, ...)

    if event == "PLAYER_LOGIN" then
        Kong_LoadSettings();
        hooksecurefunc("CreateFrame", function() frameFindTimer = 0; end)
        if Kong_Settings.enabled then
            KongUIFader:SetScript("OnUpdate", Kong_OnUpdate);
        end
    elseif event == "PLAYER_LOGOUT" then
        xpcall(Kong_Unload, function(message) Kong_Settings.error = message.."\n"..debugstack() end)
    end
end

-- Fix for minimap fading
MinimapCluster.KongAnimationGroup = MinimapCluster:CreateAnimationGroup()
MinimapCluster.KongAlphaAnimation = MinimapCluster.KongAnimationGroup:CreateAnimation("Alpha")
MinimapCluster.KongAlphaAnimation:SetScript("OnPlay", function(self) 
    if self:GetChange() > 0 then
        Minimap:Show();
    end
end)
MinimapCluster.KongAlphaAnimation:SetScript("OnFinished", function(self)
    local alpha = MinimapCluster:GetAlpha()
    if alpha == 0 then
        Minimap:Hide();
    else
        Minimap:Show();
        if alpha == 1 then
            local zoom = Minimap:GetZoom();
            Minimap:SetZoom(mod(zoom+1,6));
            Minimap:SetZoom(zoom);
        end
    end
end)

-- The event receiver frame
KongUIFader = CreateFrame("Frame");
KongUIFader:SetScript("OnEvent", Kong_OnEvent);

KongUIFader:RegisterEvent("PLAYER_LOGOUT");
KongUIFader:RegisterEvent("PLAYER_LOGIN");
